package winapi

/*****************
WIN7及以上版本以服务程序在当前用户的窗口创建一个GUI用户进程，并显示程序窗体

调用WINAPI的DLL库时，当函数要求用指针时，这样做：
var(
        hTokenDup uintptr
		hToken    uintptr
)
ret1, _, _ := WTSQueryUserToken.Call(dwSessionID, uintptr(unsafe.Pointer(&hToken)))

//获取当前处于活动状态用户的Token,指针： uintptr(unsafe.Pointer(&hToken))
/*
WTSQueryUserToken原型：BOOL WTSQueryUserToken（
  _In_ ULONG SessionId，
  _Out_ PHANDLE phToken	//这里是指针，在GO里用uintptr(unsafe.Pointer(&hToken))
）;
*****************/

import (
	"errors"
	"log"
	"os"
	"syscall"
	"unsafe"

	"github.com/CodyGuo/win"
)

const (
	MAXIMUM_ALLOWED            = 0
	CREATE_NEW_CONSOLE         = 0x00000008
	CREATE_UNICODE_ENVIRONMENT = 0x00000400

	WINSTA_ALL_ACCESS        = 0x37F
	WINSTA_ACCESSCLIPBOARD   = 0x0004
	WINSTA_ACCESSGLOBALATOMS = 0x0020
	WINSTA_CREATEDESKTOP     = 0x0008
	WINSTA_ENUMDESKTOPS      = 0x0001
	WINSTA_ENUMERATE         = 0x0100
	WINSTA_EXITWINDOWS       = 0x0040
	WINSTA_READATTRIBUTES    = 0x0002
	WINSTA_READSCREEN        = 0x0200
	WINSTA_WRITEATTRIBUTES   = 0x0010

	DESKTOP_CREATEMENU      = 0x0004 //	在桌面上创建菜单是必需的。
	DESKTOP_CREATEWINDOW    = 0x0002 //	需要在桌面上创建一个窗口。
	DESKTOP_ENUMERATE       = 0x0040 //	要列举的桌面是必需的。
	DESKTOP_HOOKCONTROL     = 0x0008 //	建立任何窗口钩子所需的。
	DESKTOP_JOURNALPLAYBACK = 0x0020 //	在桌面上执行日记回放所必需的。
	DESKTOP_JOURNALRECORD   = 0x0010 //	在桌面上执行日记记录所必需的。
	DESKTOP_READOBJECTS     = 0x0001 //	读取桌面上的对象是必需的。
	DESKTOP_SWITCHDESKTOP   = 0x0100 //	使用SwitchDesktop功能激活桌面所 必需的。
	DESKTOP_WRITEOBJECTS    = 0x0080 //在桌面上编写对象需要。
)

//https://blog.csdn.net/CMbug/article/details/52691180
//WIN7可用。服务进程在当前用户的窗口创建一个GUI用户进程，显示程序窗体
func (*Winapi) ShowUserGui(appName string) (err error) {
	var (
		Advapi32 = syscall.NewLazyDLL("Advapi32.dll")
		Kernel32 = syscall.NewLazyDLL("Kernel32.dll")
		Wtsapi32 = syscall.NewLazyDLL("Wtsapi32.dll")
		Userenv  = syscall.NewLazyDLL("Userenv.dll")

		WTSGetActiveConsoleSessionId = Kernel32.NewProc("WTSGetActiveConsoleSessionId")
		WTSQueryUserToken            = Wtsapi32.NewProc("WTSQueryUserToken")
		DuplicateTokenEx             = Advapi32.NewProc("DuplicateTokenEx")
		CreateEnvironmentBlock       = Userenv.NewProc("CreateEnvironmentBlock")
		CreateProcessAsUserW         = Advapi32.NewProc("CreateProcessAsUserW")
		WaitForSingleObject          = Kernel32.NewProc("WaitForSingleObject")
		DestroyEnvironmentBlock      = Userenv.NewProc("DestroyEnvironmentBlock")
		CloseHandle                  = Kernel32.NewProc("CloseHandle")

		hTokenDup uintptr
		hToken    uintptr //win.HANDLE = win.WM_NULL
	)

	file, err := os.Create("c:\\winapi_showusergui.log")
	if err != nil {
		log.Fatalln("fail to create winapi_showusergui.log file!")
	}
	logger := log.New(file, "", log.LstdFlags|log.Llongfile)
	logger.Println("start")

	//获取控制台会话的会话标识符
	dwSessionID, _, _ := WTSGetActiveConsoleSessionId.Call()
	logger.Println("获取控制台会话的会话标识符:", dwSessionID)
	if dwSessionID == 0 {
		logger.Println("获取控制台会话的会话标识符的错误信息:", win.GetLastError())
		err = errors.New("执行WTSGetActiveConsoleSessionId出错！" + string(win.GetLastError()))
		return err
	}

	//获取当前处于活动状态用户的Token,指针： uintptr(unsafe.Pointer(&hToken))
	ret1, _, _ := WTSQueryUserToken.Call(dwSessionID, uintptr(unsafe.Pointer(&hToken)))
	logger.Println("获取当前处于活动状态用户的Token:", ret1)
	if ret1 == 0 {
		logger.Println("获取当前处于活动状态用户的Token的错误信息:", win.GetLastError())
		CloseHandle.Call(hToken)
		err = errors.New("执行WTSQueryUserToken出错！" + string(win.GetLastError()))
		return err
	}

	//复制新的Token
	ret2, _, _ := DuplicateTokenEx.Call(hToken, MAXIMUM_ALLOWED, 0, 0, 1, uintptr(unsafe.Pointer(&hTokenDup)))
	logger.Println("复制新的Token:", ret2)
	if ret2 == 0 {
		logger.Println("复制新的Token的错误信息:", win.GetLastError())
		CloseHandle.Call(hToken)
		err = errors.New("执行DuplicateTokenEx出错！" + string(win.GetLastError()))
		return err
	}

	var pEnv uintptr //= new(uintptr) //(*uintptr)((unsafe.Pointer)(new(uintptr)))

	ret3, _, _ := CreateEnvironmentBlock.Call(uintptr(unsafe.Pointer(&pEnv)), hTokenDup, 0)
	logger.Println("创建环境信息:", ret3)
	if ret3 == 0 {
		logger.Println("创建环境信息的错误信息:", win.GetLastError())
		CloseHandle.Call(hTokenDup)
		CloseHandle.Call(hToken)
		err = errors.New("执行CreateEnvironmentBlock出错！" + string(win.GetLastError()))
		return err
	}

	//设置启动参数
	type PROCESS_INFORMATION struct {
		hProcess    uintptr
		hThread     uintptr
		dwProcessId int32
		dwThreadId  int32
	}
	type STARTUPINFO struct {
		cb              uintptr
		lpReserved      uintptr
		lpDesktop       uintptr
		lpTitle         uintptr
		dwX             int32
		dwY             int32
		dwXSize         int32
		dwYSize         int32
		dwXCountChars   int32
		dwYCountChars   int32
		dwFillAttribute int32
		dwFlags         int32
		wShowWindow     int
		cbReserved2     int
		lpReserved2     uintptr
		hStdInput       uintptr
		hStdOutput      uintptr
		hStdError       uintptr
	}
	var si STARTUPINFO
	var pi PROCESS_INFORMATION
	si.cb = unsafe.Sizeof(si)
	si.lpDesktop = win.StringToUintPtr("winsta0\\default")

	//szApp := win.StringToUintPtr("c://windows//notepad.exe")
	szApp := win.StringToUintPtr(appName)
	logger.Println("执行程序:", appName)
	//开始创建进程
	var dwCreateFlag uintptr = CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT
	ret4, _, _ := CreateProcessAsUserW.Call(hTokenDup, szApp, 0, 0, 0, 0, dwCreateFlag, pEnv, 0, uintptr(unsafe.Pointer(&si)), uintptr(unsafe.Pointer(&pi)))
	logger.Println("开始创建进程:", ret4)
	if ret4 == 0 {
		logger.Println("开始创建进程的错误信息:", win.GetLastError())
		CloseHandle.Call(hTokenDup)
		CloseHandle.Call(hToken)
		err = errors.New("执行CreateProcessAsUserW出错！" + string(win.GetLastError()))
		return err
	}

	//等待启动的进程结束
	WaitForSingleObject.Call(pi.hProcess, 999999999)

	DestroyEnvironmentBlock.Call(pEnv)
	CloseHandle.Call(hTokenDup)
	CloseHandle.Call(hToken)
	return err

}

//从service服务中启动程序，需要在Windows服务中打开窗口程序的方法.就是打开用户WinSta0和desktop,取得和用户交互的权限后再打开窗口进程
//仅能用于XP，不能用于WIN7及以上，	//https://blog.csdn.net/greystar/article/details/1398162
func (*Winapi) ShowXpGui() (err error) {
	var (
		user32       = syscall.NewLazyDLL("User32.dll")
		kernel32     = syscall.NewLazyDLL("Kernel32.dll")
		GetLastError = kernel32.NewProc("GetLastError")
		//CreateProcessW          = kernel32.NewProc("CreateProcessW")
		GetProcessWindowStation = user32.NewProc("GetProcessWindowStation")
		GetThreadDesktop        = user32.NewProc("GetThreadDesktop")
		GetCurrentThreadID      = kernel32.NewProc("GetCurrentThreadId")
		OpenWindowStationW      = user32.NewProc("OpenWindowStationW")
		SetProcessWindowStation = user32.NewProc("SetProcessWindowStation")
		OpenDesktopW            = user32.NewProc("OpenDesktopW")
		SetThreadDesktop        = user32.NewProc("SetThreadDesktop")
		CloseWindowStation      = user32.NewProc("CloseWindowStation")
		CloseDesktop            = user32.NewProc("CloseDesktop")
		lpszWinSta              uintptr
		lpszDesktop             uintptr
	)
	file, err := os.Create("c:\\debug.log")
	if err != nil {
		log.Fatalln("fail to create test.log file!")
		err = errors.New("fail to create test.log file!")
		return err
	}
	logger := log.New(file, "", log.LstdFlags|log.Llongfile)

	hwinstaCurrent, _, _ := GetProcessWindowStation.Call()
	ctid, _, _ := GetCurrentThreadID.Call()
	hdeskCurrent, _, _ := GetThreadDesktop.Call(ctid)
	//lpszWinSta = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("winsta0")))
	lpszWinSta = win.StringToUintPtr("winsta0")
	//fInherit := win.StringToUintPtr("false") //uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("false")))
	hwinsta, _, _ := OpenWindowStationW.Call(lpszWinSta, 0, WINSTA_ACCESSCLIPBOARD|
		WINSTA_ACCESSGLOBALATOMS|
		WINSTA_CREATEDESKTOP|
		WINSTA_ENUMDESKTOPS|
		WINSTA_ENUMERATE|
		WINSTA_EXITWINDOWS|
		WINSTA_READATTRIBUTES|
		WINSTA_READSCREEN|
		WINSTA_WRITEATTRIBUTES)
	if hwinsta == 0 {
		logger.Println("OpenWindowStationW函数调用失败，返回值为NULL或0")
		err = errors.New("OpenWindowStationW函数调用失败，返回值为NULL或0")
		return err
	} else {
		logger.Println("OpenWindowStationW函数调用成功")
		logger.Println(hwinsta)
	}
	ret, _, _ := SetProcessWindowStation.Call(hwinsta)
	if ret == 0 {
		logger.Println("SetProcessWindowStation.Call(hwinsta)函数调用失败，返回值为NULL或0")
		err = errors.New("SetProcessWindowStation.Call(hwinsta)函数调用失败，返回值为NULL或0")
		return err
	} else {
		logger.Println("SetProcessWindowStation.Call(hwinsta)函数调用成功")
		logger.Println(ret)
	}
	//lpszDesktop = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("default"))) //
	lpszDesktop = win.StringToUintPtr("default")
	hdesk, _, _ := OpenDesktopW.Call(lpszDesktop, 0, 0, DESKTOP_CREATEMENU|
		DESKTOP_CREATEWINDOW|
		DESKTOP_ENUMERATE|
		DESKTOP_HOOKCONTROL|
		DESKTOP_JOURNALPLAYBACK|
		DESKTOP_JOURNALRECORD|
		DESKTOP_READOBJECTS|
		DESKTOP_SWITCHDESKTOP|
		DESKTOP_WRITEOBJECTS)

	if hdesk == 0 {
		logger.Println("OpenDesktopW函数调用失败，返回值为NULL或0")
		logger.Println(GetLastError.Call())
		err = errors.New("OpenDesktopW函数调用失败，返回值为NULL或0")
		return err
	} else {
		logger.Println("OpenDesktopW函数调用成功")
		logger.Println(hdesk)
	}
	ret2, _, _ := SetThreadDesktop.Call(hdesk)

	if ret2 == 0 {
		logger.Println("SetThreadDesktop.Call(hdesk)函数调用失败，返回值为NULL或0")
		logger.Println(GetLastError.Call())
		err = errors.New("SetThreadDesktop.Call(hdesk)函数调用失败，返回值为NULL或0")
		return err
	} else {
		logger.Println("SetThreadDesktop.Call(hdesk)函数调用成功")
		logger.Println(ret2)
	}
	//在这里shellexec
	var lpCmdLine *byte
	lpCmdLine = win.StringToBytePtr("c://windows//notepad.exe")
	ret3 := win.WinExec(lpCmdLine, 5)
	if ret3 == 0 {
		logger.Println("win.WinExec(lpCmdLine, 5)函数调用失败，返回值为NULL或0")
		logger.Println(GetLastError.Call())
		err = errors.New("win.WinExec(lpCmdLine, 5)函数调用失败，返回值为NULL或0")
		return err
	} else {
		logger.Println("win.WinExec(lpCmdLine, 5)函数调用成功")
		logger.Println(ret3)
	}
	SetProcessWindowStation.Call(hwinstaCurrent)
	SetThreadDesktop.Call(hdeskCurrent)
	CloseWindowStation.Call(hwinsta)
	CloseDesktop.Call(hdesk)
	return err
}
